package hehe.router.easy.rule;

import hehe.router.Controller;
import hehe.router.RequestUrl;
import hehe.router.util.RouteHelper;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 正则路由
 *<B>说明：</B>
 *<pre>
 * 路由基本格式:控制器名称/控制器方法名称,比如 index/index
 *
 *</pre>
 *<B>示例：</B>
 *<pre>
 *
 * 解析路由地址
 * ```java
 * Map<String,Object> attrs = new HashMap<>();
 * attrs.put("pathinfoRule","<controller:\\\\w+>/<action:\\\\w+>/<user:\\\\d+>");
 * attrs.put("urlRule","<controller>/<action>");
 * PatternRule patternRule = new PatternRule(attrs);
 * Controller controller = patternRule.parseRequest("index/add/1",null);
 * if (controller == null) {
 *   System.out.println("未找到对应的路由地址");
 * }
 *
 * 生成路由地址
 * Map<String,Object> params = new HashMap<>();
 * params.put("id","1");
 * String url = patternRule.parseUrL("index/add",params);
 *
 * url 对应为:index/add?id=1,或者index/add/id/1
 *
 *
 * ```
 *
 *</pre>
 *<B>日志：</B>
 *<pre>
 *  略
 *</pre>
 *<B>注意事项：</B>
 *<pre>
 *  略
 *</pre>
 */
public class PatternRule extends Rule
{

    /**
     * 正则表达式符号/
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public static final String REG_SPLIT = "/";

    /**
     * pathinfo 参数匹配正则表达式/
     *<B>说明：</B>
     *<pre>
     *  pathinfo 基本格式
     *</pre>
     */
    private static final String PATHINFO_PARAMS_REGEX = "<(\\w+):?([^>]+)?>";

    /**
     * URL参数匹配正则表达式/
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static final String URL_PARAMS_REGEX = "<(\\w+)>";

    /**
     * 参数左边分隔符
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static final String PARAMS_LEFT_FLAG = "<";

    /**
     * 参数左边分隔符
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private static final String PARAMS_RIGHT_FLAG = ">";

    /**
     * pathinfo　规则表达式
     *<B>说明：</B>
     *<pre>
     *  基本格式:<controller:\\w+>/<action:\\w+>
     *</pre>
     */
    private String pathinfoRule;

    /**
     * pathinfo 正则表达式
     *<B>说明：</B>
     *<pre>
     *  基本格式:^(?<controller>\w+)/(?<action>\w+)$
     *</pre>
     */
    private String pathinfoRegex;

    /**
     * 保存pathinfo 抽取的参数
     *<B>说明：</B>
     *<pre>
     * 比如:pathinfoRule:<controller:\w+>/<id:\d+>
     * 最终pathinfoParams 存储格式:
     * {controller=\\w+, id=\\d+}
     *</pre>
     */
    private Map<String,String> pathinfoParams = new HashMap<>();

    /**
     * url 规则
     *<B>说明：</B>
     *<pre>
     *  基本格式:<controller>/<action>
     *
     *  比如:
     *  pathinfoRule:<controller:\w+>/<id:\d+>
     *  url 地址可以使用controller,id 参数
     *
     *  url 最终格式可以有:
     *  post/<id>
     *  <controller>/<id>
     *</pre>
     */
    protected String urlRule;

    /**
     * url 正则表达式
     *<B>说明：</B>
     *<pre>
     * 用于匹配url 的正则表达式
     * 基本格式:^(?<controller>\w+)/(?<action>\w+)$
     *</pre>
     */
    private String urlRegex;

    /**
     * url 替换模板
     *<B>说明：</B>
     *<pre>
     * 基本格式:<controller>/<action>
     *</pre>
     */
    private String urlTemplate;

    /**
     * 保存url 抽取的参数
     *<B>说明：</B>
     *<pre>
     * 基本格式:{controller=<controller>, action=<action>}
     *</pre>
     */
    private Map<String,String> urlParams = new HashMap<>();


    /**
     * 用于解析或创建URL时，处理主机信息的部分
     *<B>说明：</B>
     *<pre>
     *  如 http://www.digpage.com
     *</pre>
     */
    private String host;

    /**
     * 默认GET参数
     *<B>说明：</B>
     *<pre>
     * 以一组键值对数组指定若干GET参数，在当前规则用于解析请求时，
     * 这些GET参数会被注入到GET中去
     *</pre>
     */
    private Map<String,String> defaults  = new HashMap<>();


    public PatternRule(Map<String,Object> attrs)
    {
        super(attrs);
        this.init();
    }

    /**
     * 初始化
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private void init()
    {
        // 提取
        this.parseRule();
    }

    /**
     * 解析pathinfo 规则
     *<B>说明：</B>
     *<pre>
     *  思路
     *  pathinfo 基本格式：<controller:\w+>/<id:\d+>
     *  分离controller,id 等参数
     *</pre>
     */
    protected void parseRule()
    {

        this.formatPathinfoRule();
        this.formatUrlRule();

        this.urlRegex = this.buildUrlRegex();
        this.pathinfoRegex = this.buildPathinfoRegex();
    }

    /**
     * 构建url正则表达式
     *<B>说明：</B>
     *<pre>
     * 略
     *</pre>
     */
    private String buildUrlRegex()
    {
        String urlRuleTemp = this.urlRule;
        String name;
        for (Map.Entry<String, String> params : this.urlParams.entrySet()) {
            name = params.getKey();
            // 构建名称复活组
            urlRuleTemp = urlRuleTemp.replaceAll( PARAMS_LEFT_FLAG + name + PARAMS_RIGHT_FLAG
                    ,"(?" + PARAMS_LEFT_FLAG + name + PARAMS_RIGHT_FLAG + this.pathinfoParams.get(name) + ")");
        }

        return "^" + RouteHelper.trim(urlRuleTemp, REG_SPLIT) + "$";
    }

    /**
     * 构建pathinfo 正则表达式
     *<B>说明：</B>
     *<pre>
     * 用于匹配parseRequest 方法　pathinfo　参数
     *</pre>
     */
    private String buildPathinfoRegex()
    {
        String urlRegexTemp = this.pathinfoRegex.replaceAll(PATHINFO_PARAMS_REGEX, "<$1>");
        this.urlTemplate = urlRegexTemp;
        String name,pattern;
        for (Map.Entry<String, String> params : this.pathinfoParams.entrySet()) {
            name = params.getKey();
            pattern = params.getValue();
            // 构建名称复活组
            urlRegexTemp = urlRegexTemp.replaceAll( PARAMS_LEFT_FLAG + name + PARAMS_RIGHT_FLAG
                    ,"(?" + PARAMS_LEFT_FLAG + name + PARAMS_RIGHT_FLAG + pattern + ")");
        }

        return "^" + RouteHelper.trim(urlRegexTemp, REG_SPLIT) + "$";
    }

    /**
     * 格式化PathinfoRule 路由规则
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected void formatPathinfoRule()
    {
        // 组织pathinfoRule
        this.pathinfoRegex = RouteHelper.trim(this.pathinfoRule,REG_SPLIT);

        if (this.host != null) {
            this.host = RouteHelper.rtrim(this.host,REG_SPLIT);
            this.pathinfoRegex = RouteHelper.rtrim(this.host + REG_SPLIT + this.pathinfoRegex,REG_SPLIT);
        }

        // 默认规则
        if (this.pathinfoRegex == null) {
            this.pathinfoRegex = "^$";
        }

        // 提取pathinfo参数
        this.formatPathinfoParams();
    }

    /**
     * 格式化UrlRule 路由规则
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected void formatUrlRule()
    {
        this.formatUrlParams();
    }

    /**
     * 格式化pathinfoRule参数
     *<B>说明：</B>
     *<pre>
     *  比如,"<controller:\\\\w+>/<action:\\\\w+>"
     *  返回参数格式:{"controller":"\\w+","action":"\\w+"}
     *</pre>
     */
    protected void formatPathinfoParams()
    {
        // <controller:\w+>/<id:\d+> 将controller,id 提取出来
        Matcher pathinfoRuleMatcher = Pattern.compile(PATHINFO_PARAMS_REGEX)
                .matcher(this.pathinfoRegex);

        // 定义参数匹配项
        String name,pattern;
        while (pathinfoRuleMatcher.find()){
            for (int i = 1; i <= pathinfoRuleMatcher.groupCount();i = i + 2) {
                name = pathinfoRuleMatcher.group(i);
                pattern = pathinfoRuleMatcher.group(i + 1);
                this.pathinfoParams.put(name,pattern);
            }
        }
    }

    /**
     * 格式化urlRule 参数
     *<B>说明：</B>
     *<pre>
     *  比如,"<controller>/<action>"
     *  返回参数格式:{"controller":"<controller>","action":"<action>"}
     *</pre>
     */
    private void formatUrlParams()
    {
        // 提取urlRule 参数
        this.urlRule = RouteHelper.trim(this.urlRule,REG_SPLIT);
        Matcher urlRuleMatcher = Pattern.compile(URL_PARAMS_REGEX).matcher(this.urlRule);
        String name;
        while (urlRuleMatcher.find()) {
            for (int i = 1; i <= urlRuleMatcher.groupCount();i = i + 1) {
                name = urlRuleMatcher.group(i);
                this.urlParams.put(name,PARAMS_LEFT_FLAG + name + PARAMS_RIGHT_FLAG);
            }
        }
    }

    @Override
    public Controller parseRequest(String pathinfo)
    {

        Matcher pathinfoMatcher = Pattern.compile(this.pathinfoRegex).matcher(pathinfo);

        // pathinfo 未匹配
        if (!pathinfoMatcher.matches()) {
            return null;
        }

        // 定义url 控制器的其他参数
        Map<String,String> controllerParams = new HashMap<>();
        controllerParams.putAll(this.defaults);

        Map<String,String> controlleReplaceParams = new HashMap<>();
        // 获取匹配到获组的参数
        Map<String,String> params = RouteHelper.getMatcherParams(pathinfoMatcher,this.pathinfoParams);

        String name;
        for (Map.Entry<String, String> param : params.entrySet()) {
            name = param.getKey();
            if (this.urlParams.containsKey(name)) {
                controllerParams.remove(name);
                controlleReplaceParams.put(this.urlParams.get(name),param.getValue());
            } else {
                controllerParams.put(name, param.getValue());
            }
        }

        if (this.urlRule != null) {
            String url = RouteHelper.replaceMap(this.urlRule,controlleReplaceParams);
            return new Controller(url,controllerParams);
        }

        return null;
    }


    @Override
    public RequestUrl parseUrL(String url, Map<String, String> params)
    {
        Map<String,String> requestUrlParams = new HashMap<>();

        // 匹配url ,如何满足表达式,则读取对应参数
        if (this.urlRule != url) {
            Matcher urlMatcher = Pattern.compile(this.urlRegex).matcher(url);
            if (!urlMatcher.matches()) {
                return null;
            }

            requestUrlParams = RouteHelper.getMatcherParams(urlMatcher,this.urlParams);
        }

        // 合并传入参数,默认参数
        requestUrlParams.putAll(this.defaults);
        requestUrlParams.putAll(params);

        String name;
        // 定义url 模板参数　
        String uri = this.urlTemplate;
        for (Map.Entry<String, String> param : requestUrlParams.entrySet()) {
            name = param.getKey();
            if (this.urlParams.containsKey(name)) {
                uri = uri.replaceAll(this.urlParams.get(name),param.getValue());
            }
        }

        // 清除urlParams 的参数
        for (Map.Entry<String, String> param : this.urlParams.entrySet()) {
            requestUrlParams.remove(param.getKey());
        }

        return new RequestUrl(uri,requestUrlParams);
    }
}
